package com.jl;

import com.google.auto.service.AutoService;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableSet;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.yaml.snakeyaml.Yaml;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import java.io.*;
import java.lang.annotation.Annotation;
import java.util.*;

/**
 * 自动生成spring.factories文件
 */
@Slf4j
@AutoService(Processor.class)
@SupportedOptions({"debug", "verify"})
public class JLSpringFactorles extends AbstractProcessor {

    private static final String SERVICES_PATH = "META-INF/spring.factories";

    private Map<String, Set<String>> providers = new HashMap<>();

    /**
     * 是否迭代
     */
    private static boolean father = false;

    /**
     * 会被回调处理的注解
     */
    private List<Class<? extends Annotation>> list = Arrays.asList(
            Component.class, Service.class
            , Controller.class, RestController.class
            , RestControllerAdvice.class, Configuration.class,
            FeignClient.class
    );

    /**
     * 初始化会被回调处理的注解
     *
     * @return
     */
    @Override
    public ImmutableSet<String> getSupportedAnnotationTypes() {
        Set<String> strings = new HashSet<>();
        for (Class<? extends Annotation> classz : list) {
            strings.add(classz.getName());
        }
        return ImmutableSet.copyOf(strings);
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * 处理过程
     *
     * @param annotations
     * @param roundEnv
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        try {
            if (roundEnv.processingOver()) {
                generateConfigFiles();
            } else {
                for (Class<? extends Annotation> annotationClass : list) {
                    boolean exec = exec(roundEnv, annotationClass);
                    if (exec) {
                        continue;
                    }
                }
            }
        } catch (Exception e) {
            StringWriter writer = new StringWriter();
            e.printStackTrace(new PrintWriter(writer));
            fatalError(writer.toString());
        }
        return true;
    }

    /**
     * @param roundEnv        类对象？
     * @param annotationClass 要被扫描的注解
     */
    private boolean exec(RoundEnvironment roundEnv, Class<? extends Annotation> annotationClass) {
        //如果添加了classz的注解则能获取到set集合
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotationClass);
        for (Element e : elements) {
            TypeElement providerImplementer = (TypeElement) e;
            //过滤增加了@RestController的接口
            if ((annotationClass == RestController.class || annotationClass == RestController.class)
                    && providerImplementer.getKind().isInterface()) {
                continue;
            }
            //获取类注解集合
            List<? extends AnnotationMirror> annotationMirrors = providerImplementer.getAnnotationMirrors();
            //判断注解是否可生成spring.factories文件
            boolean checkFatherComponent = father ? checkFatherComponent(annotationMirrors) : checkAnnotation(annotationMirrors, annotationClass);
            //放入集合
            if (checkFatherComponent) {
                TypeElement providerType = processingEnv.getElementUtils().getTypeElement(EnableAutoConfiguration.class.getName());
                providers.computeIfAbsent(getBinaryName(providerType), key -> new HashSet<>())
                        .add(getBinaryName(providerImplementer));
                return true;
            }
        }
        return false;
    }

    /**
     * 判断注解是否可生成spring.factories文件
     *
     * @param annotationMirrors
     * @param annotationClass
     * @return
     */
    public boolean checkAnnotation(List<? extends AnnotationMirror> annotationMirrors, Class<? extends Annotation> annotationClass) {
        for (AnnotationMirror annotationMirror : annotationMirrors) {
            String thisName = annotationMirror.getAnnotationType().asElement().getSimpleName().toString();
            String annotationName = annotationClass.getSimpleName();
            if (thisName.equals(annotationName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 递归判断注解是否可生成spring.factories文件
     *
     * @param annotationMirrors
     * @return
     */
    public boolean checkFatherComponent(List<? extends AnnotationMirror> annotationMirrors) {
        for (AnnotationMirror annotationMirror : annotationMirrors) {
            //判断自身类的注解
            String simpleName = annotationMirror.getAnnotationType().asElement().getSimpleName().toString();
            if (simpleName.equals("Documented") || simpleName.equals("Retention") || simpleName.equals("Target")) {
                continue;
            }
            if (simpleName.equals("Component") || simpleName.equals("FeignClient")) {
                return true;
            }
            //判断注解的父注解
            List<? extends AnnotationMirror> annotationMirrorsp = annotationMirror.getAnnotationType().asElement().getAnnotationMirrors();
            boolean b = checkFatherComponent(annotationMirrorsp);
            if (b) {
                return b;
            }
        }
        return false;
    }

    private void generateConfigFiles() {
        Filer filer = processingEnv.getFiler();
        try {
            FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
                    SERVICES_PATH);
            OutputStream out = fileObject.openOutputStream();
            Files.writeServiceFile(providers, out);
            out.close();
            log("Wrote to: " + fileObject.toUri());
        } catch (IOException e) {
            fatalError("Unable to create " + SERVICES_PATH + ", " + e);
            return;
        }
    }

    private String getBinaryName(TypeElement element) {
        return getBinaryNameImpl(element, element.getSimpleName().toString());
    }

    private String getBinaryNameImpl(TypeElement element, String className) {
        Element enclosingElement = element.getEnclosingElement();
        if (enclosingElement instanceof PackageElement) {
            PackageElement pkg = (PackageElement) enclosingElement;
            if (pkg.isUnnamed()) {
                return className;
            }
            return pkg.getQualifiedName() + "." + className;
        }
        TypeElement typeElement = (TypeElement) enclosingElement;
        return getBinaryNameImpl(typeElement, typeElement.getSimpleName() + "$" + className);
    }

    private void log(String msg) {
        if (processingEnv.getOptions().containsKey("debug")) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg);
        }
    }

    private void fatalError(String msg) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "FATAL ERROR: " + msg);
    }


    public static class Files {
        static void writeServiceFile(Map<String, Set<String>> services, OutputStream output) throws IOException {
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, Charsets.UTF_8));
            for (Map.Entry<String, Set<String>> entry : services.entrySet()) {
                writer.write(entry.getKey() + "=\\");
                boolean first = true;
                for (String service : entry.getValue()) {
                    if (first) {
                        writer.newLine();
                        writer.write(service);
                        first = false;
                    } else {
                        writer.write(",\\");
                        writer.newLine();
                        writer.write(service);
                    }
                }
                writer.newLine();
            }
            writer.flush();
        }

    }


    /**
     * 根据文件名获取yml的文件内容
     *
     * @param keys 第一个参数对应第一个key，第二个参数对应第二个key 比如spring.name下的所有 就是两个参数、
     *             getYmlByFileName(bootstrap_file,"spring", "name");
     * @return
     */
    private static Map<String, String> getYmlByFileName(String filePath, String... keys) {
        Map<String, String> result = new HashMap<>();
        InputStream in = null;
        try {
            File file = ResourceUtils.getFile(filePath);
            in = new BufferedInputStream(new FileInputStream(file));
            Yaml props = new Yaml();
            Object obj = props.loadAs(in, Map.class);
            Map<String, Object> param = (Map<String, Object>) obj;

            for (Map.Entry<String, Object> entry : param.entrySet()) {
                String key = entry.getKey();
                Object val = entry.getValue();
                if (keys.length != 0 && !keys[0].equals(key)) {
                    continue;
                }
                if (val instanceof Map) {
                    result = forEachYaml(key, (Map<String, Object>) val, 1, keys);
                } else {
                    result.put(key, val.toString());
                }
            }
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }

    /**
     * 遍历yml文件，获取map集合
     *
     * @param key_str
     * @param obj
     * @param i
     * @param keys
     * @return
     */
    private static Map<String, String> forEachYaml(String key_str, Map<String, Object> obj, int i, String... keys) {
        Map<String, String> result = new HashMap<>();
        for (Map.Entry<String, Object> entry : obj.entrySet()) {
            String key = entry.getKey();
            Object val = entry.getValue();
            if (keys.length > i && !keys[i].equals(key)) {
                continue;
            }
            String str_new = "";
            if (!StringUtils.isEmpty(key_str)) {
                str_new = key_str + "." + key;
            } else {
                str_new = key;
            }
            if (val instanceof Map) {
                forEachYaml(str_new, (Map<String, Object>) val, ++i, keys);
                i--;
            } else {
                result.put(str_new, val.toString());
            }
        }
        return result;
    }

}